﻿using Devil;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;

namespace GameToolkit.Editor
{
    public class StoryGraph : GraphView, IWindowGraph, IDisposable, IGraphViewAutoConnector
    {
        public class UxmlFac : UxmlFactory<StoryGraph, UxmlTraits> { }

        public EditorWindow AttachedWindow { get; internal set; }

        public event Action<StoryGraph> OnIsDirty;

        // 将创建的节点连接至
        AutoCreateFilter mAutoCreateFilter;

        bool mReselect;

        // 通知
        GUIContent mNtf;
        StartNode mRoot;
        ContentZoomer mZoomer;
        RectangleSelector mRectSelector;
        Label mScale;
        Label mDescription;
        List<Port> mCompatiblePorts = new List<Port>();

        bool mReopen;
        // 上下文菜单鼠标位置
        Vector2 mMouseContextPosition;
        bool mIsDirty;
        public bool IsDirty
        {
            get { return mIsDirty; }
            set
            {
                if (mIsDirty != value)
                {
                    mIsDirty = value;
                    OnIsDirty?.Invoke(this);
                }
            }
        }


        TaskGraphAsset mAsset;
        TaskGraph mDebugGraph;
        public TaskGraphAsset asset
        {
            get { return mAsset; }
            set
            {
                //if(mAsset != value)
                {
                    mAsset = value;
                    ParallelUtils.Schedule(InitGraph, true);
                    //mReopen = true;
                    //LoadGraphNodes();
                }
            }
        }

        public StartNode startNode => mRoot;

        StoryGraphSelection mSelection;

        IEnumerator InitGraph()
        {
            while (!ParallelUnity.IsEditorInitialized)
                yield return null;
            mReopen = true;
            LoadGraphNodes();
            if(mDebugGraph != null)
            {
                BindRuntimeGraph();
            }
        }

        void BindRuntimeGraph()
        {
            if (mDebugGraph == null)
            {
                foreach(var node in this.nodes)
                {
                    if (node is StoryNodeVisual snode)
                        snode.BindEntity(null);
                }
            }
            else
            {
                foreach (var node in this.nodes)
                {
                    if (node is StoryNodeVisual snode)
                        snode.BindEntity(mDebugGraph.GetEntity(snode.nodeAsset as TaskNode));
                }
            }
        }

        public void BindRuntimeGraph(TaskGraph graph)
        {
            if (graph != mDebugGraph)
            {
                mDebugGraph = graph;
                if (graph != null && graph.asset != mAsset)
                {
                    asset = graph.asset;
                }
                else
                {
                    BindRuntimeGraph();
                }
                UpdateDescription();
            }
        }

        //BehaviourNodeInspector mInspector;
        //Toggle mInspectorToggle;

        //internal Toggle InspectorToggle
        //{
        //    get { return mInspectorToggle; }
        //    set
        //    {
        //        if (mInspectorToggle != value)
        //        {
        //            if (mInspectorToggle != null)
        //                mInspectorToggle.UnregisterValueChangedCallback(OnToggleInspector);
        //            mInspectorToggle = value;
        //            if (mInspectorToggle != null)
        //            {
        //                if (mAsset != null)
        //                    mInspectorToggle.value = mAsset.m_ShowInspector;
        //                mInspectorToggle.RegisterValueChangedCallback(OnToggleInspector);
        //                mInspector.visible = ShouldInspectorVisible();
        //            }
        //        }
        //    }
        //}


        public StoryGraph() : base()
        {
            mNtf = new GUIContent();
            var view = contentContainer;
            if (view != null)
            {
                view.name = "Viewport";
            }

            name = "story-graph";

            var gap = new Length(10, LengthUnit.Pixel);

            mScale = new Label();
            mScale.name = "graph-scale";
            mScale.pickingMode = PickingMode.Ignore;
            hierarchy.Insert(0, mScale);

            mDescription = new Label();
            mDescription.name = "graph-desc";
            mDescription.pickingMode = PickingMode.Ignore;
            mDescription.text = "Asset Info";
            //mDescription.text = "asset path";
            hierarchy.Insert(0, mDescription);

            var grid = new GridBackground();
            grid.name = "graph-grid";
            hierarchy.Insert(0, grid);

            var clampWindow = new VisualElement();
            //clampWindow.SetEnabled(false);
            clampWindow.pickingMode = PickingMode.Ignore;
            clampWindow.name = "clamp-window";
            hierarchy.Add(clampWindow);

            mRoot = new StartNode();
            AddElement(mRoot);
            mRoot.SetPosition(new Rect(20, 200, 1, 1));

            var styles = BehaviourTreeGraphUtils.GetStyleSheet();
            if (styles != null)
                styleSheets.Add(styles);

            nodeCreationRequest = OnCreateNode;
            viewTransformChanged = OnTransformChanged;
            graphViewChanged = OnGraphViewChanged;
            //serializeGraphElements = OnSerializeGraphElement;
            //unserializeAndPaste = OnDeserializeGraphElement;
            //canPasteSerializedData = CanPasteGraphElement;

            mZoomer = new ContentZoomer();
            mZoomer.maxScale = 3f;
            mZoomer.minScale = 0.2f;

            mRectSelector = new RectangleSelector();

            this.AddManipulator(new SelectionDragger());
            this.AddManipulator(new ContentDragger());

            this.AddManipulator(mRectSelector);
            this.AddManipulator(mZoomer);

            this.RegisterCallback<MouseMoveEvent>(OnMouseMove);
            this.RegisterCallback<MouseDownEvent>(OnMouseDown);
            this.RegisterCallback<KeyDownEvent>(OnKeyDown);

            OnTransformChanged(this);
            
        }

        internal void Relayout()
        {
            var reselect = mReselect;
            mReselect = false;
            if (mReopen)
            {
                mReopen = false;
                FrameAll();
            }
            if(reselect)
            {
                if(mSelection == null)
                {
                    mSelection = ScriptableObject.CreateInstance<StoryGraphSelection>();
                    mSelection.name = "Story Node";
                    mSelection.hideFlags |= HideFlags.DontSave;
                }
                mSelection.GetSelections(this);
            }
            //mInspector.GetSelections();
            //mInspector.visible = ShouldInspectorVisible();// mInspector.TargetCount > 0 && mInspectorToggle != null && mInspectorToggle.value;
        }

        internal Edge NewEdge<T>(Port input, Port output) where T : Edge, new()
        {
            if (input == null || output == null || input.node == output.node)
                return null;
            var edge = new T();
            edge.input = input;
            edge.output = output;
            input.Connect(edge);
            output.Connect(edge);
            AddElement(edge);
            return edge;
        }

        private void OnCreateNode(NodeCreationContext context)
        {
            if (EditorApplication.isPlayingOrWillChangePlaymode)
                return;
            mAutoCreateFilter = null;
            if (EditorWindow.focusedWindow == AttachedWindow)
            {
                var providor = SearchTreeLib<StoryNodeLib>.Get();
                providor.Filter = null;
                providor.PerformOnce(CreateStoryNode);
                SearchWindow.Open(new SearchWindowContext(context.screenMousePosition, 260), providor);
            }
        }

        public void CreateNode(AutoCreateFilter filter, Vector2 pos)
        {
            if (EditorApplication.isPlayingOrWillChangePlaymode)
                return;
            if (EditorWindow.focusedWindow == AttachedWindow)
            {
                mAutoCreateFilter = filter;
                var providor = StoryNodeLib.Get();
                providor.Filter = mAutoCreateFilter == null ? null : mAutoCreateFilter.nodeFilter;
                providor.PerformOnce(CreateStoryNode);
                var root = AttachedWindow.rootVisualElement;
                Vector2 screenMousePosition = this.ChangeCoordinatesTo(root, pos) + AttachedWindow.position.position;
                SearchWindow.Open(new SearchWindowContext(screenMousePosition, 260), providor);
            }
            else
            {
                mAutoCreateFilter = null;
            }
        }

        internal Node CreateGraphNode(StoryNodeLib.NodeInfo nodeInfo, TaskGraphAsset.NodeAsset context, Vector2 initPos)
        {
            if (context == null)
            {
                context = nodeInfo.WrapType.Create() as TaskGraphAsset.NodeAsset;
                if (context != null)
                {
                    //asset._guid = GUID.Generate().ToString();
                    context.m_Position = initPos;
                }
            }
            if (context == null || nodeInfo is not StoryNodeLib.StoryNodeInfo)
                return null;
            Node node = null;
            if (context is TaskNode snode)
                node = new StoryNodeVisual((StoryNodeLib.StoryNodeInfo)nodeInfo, snode);
            return node;
            //if (NodeType == EBTNodeType.Behaviour || NodeType == EBTNodeType.Controller)
            //    return new ExecutableNodeVisual(this, context);
            //else if (NodeType == EBTNodeType.Register || NodeType == EBTNodeType.RegisterRef)
            //    return new RegisterNodeVisual(this, context);
            //else
            //    return new LogicNodeVisual(this, context);
        }

        private bool CreateStoryNode(StoryNodeLib providor, SearchTreeEntry entry, SearchWindowContext context)
        {
            var info = entry.userData as SearchTreeLib<StoryNodeLib>.NodeInfo;
            if (info == null)
                return false;
            var connect = mAutoCreateFilter;
            mAutoCreateFilter = null;
            var windowRoot = AttachedWindow.rootVisualElement;
            var nodePos = windowRoot.parent.ChangeCoordinatesTo(contentViewContainer, context.screenMousePosition - AttachedWindow.position.position);
            var node = CreateGraphNode(info, null, nodePos);
            if (node != null)
            {
                this.AddElement(node);
                if (connect != null && connect.autoConnectPort != null)
                {
                    if (node is StoryNodeVisual snode)
                    {
                        if (connect.autoConnectPort.direction == Direction.Output)
                            NewEdge<Edge>(snode.startPort, connect.autoConnectPort);
                    }
                }
                ClearSelection();
                AddToSelection(node);
                IsDirty = true;
            }
            return true;
        }

        private void OnTransformChanged(GraphView graphView)
        {
            var scale = graphView.viewTransform.scale;
            mScale.text = string.Format("[1 : {0:0.0}]", scale.x);
        }

        private GraphViewChange OnGraphViewChanged(GraphViewChange graphViewChange)
        {
            var changeData = graphViewChange;
            var delNum = changeData.elementsToRemove == null ? 0 : changeData.elementsToRemove.Count;
            var edgeNum = changeData.edgesToCreate == null ? 0 : changeData.edgesToCreate.Count;
            var movNum = changeData.movedElements == null ? 0 : changeData.movedElements.Count;
            var dirty = delNum > 0 || edgeNum > 0 || movNum > 0;
            if (mAsset != null)
            {
                IsDirty |= dirty;
            }
            if (delNum > 0)
            {
                for (int i = 0; i < delNum; i++)
                {
                    var node = changeData.elementsToRemove[i] as StoryNodeVisual;
                    if (node != null)
                        node.Release();
                }
            }
            return changeData;
        }

        private void OnKeyDown(KeyDownEvent evt)
        {
            if (evt.keyCode == KeyCode.Delete)
                DeleteSelectionOperation("delete", AskUser.AskUser);
        }

        private void OnMouseDown(MouseDownEvent evt)
        {
            mMouseContextPosition = evt.localMousePosition;
        }

        private void OnMouseMove(MouseMoveEvent evt)
        {
            mMouseContextPosition = evt.localMousePosition;
        }

        public override void AddToSelection(ISelectable selectable)
        {
            base.AddToSelection(selectable);
            mReselect = true;
        }

        public override void RemoveFromSelection(ISelectable selectable)
        {
            base.RemoveFromSelection(selectable);
            mReselect = true;
        }

        public override void ClearSelection()
        {
            base.ClearSelection();
            mReselect = true;
        }

        internal void SaveGraph(bool saveAs)
        {
            bool saved = false;
            if (mAsset != null && !saveAs)
            {
                saved = true;
                this.Save(mAsset);
                IsDirty = false;
            }
            else
            {
                var path = EditorUtility.SaveFilePanelInProject("Save Task Graph", "TaskGraph", "storygraph", "Save Task Graph");
                if (!string.IsNullOrEmpty(path))
                {
                    saved = true;
                    var asset = ScriptableObject.CreateInstance<TaskGraphAsset>();
                    asset.name = NativeFileSystem.GetFileName(path, true);
                    this.Save(asset);
                    var json = JsonSerializedObject.ExportFrom(asset);
                    NativeFileSystem.WriteAllText(path, json.ToJson(), Encoding.UTF8);
                    AssetDatabase.ImportAsset(path);
                    var inst = AssetDatabase.LoadAssetAtPath<TaskGraphAsset>(path);
                    if (inst != asset && inst != null)
                    {
                        GlobalUtil.Destroy(asset);
                    }
                    else
                    {
                        inst = asset;
                    }
                    ((StoryGraphEditor)AttachedWindow).storyGraphAsset = inst;
                    mReopen = false;
                }
            }
            if (saved && AttachedWindow != null)
            {
                mNtf.text = "已保存!";
                AttachedWindow.ShowNotification(mNtf, 1f);
            }
        }

        Node GetNode(IList<GraphElement> graphNodes, TaskGraphAsset.NodeAsset nodeAsset)
        {
            for (int i = 0; i < graphNodes.Count; i++)
            {
                if (graphNodes[i] is IStoryNodeVisual snode && snode.nodeAsset == nodeAsset)
                    return (Node)graphNodes[i];
            }
            return null;
        }

        void UpdateDescription()
        {
            if (mAsset == null)
            {
                mDescription.text = "<none>";
            }
            else
            {
                var path = AssetDatabase.GetAssetPath(mAsset);
                if (string.IsNullOrEmpty(path))
                    path = $"{mAsset.name} ({mAsset.GetInstanceID().ToString("XX")})";
                if (mDebugGraph != null)
                    path = $"[{mDebugGraph.name}] path";
                mDescription.text = path;
            }
        }

        internal void LoadGraphNodes()
        {
            var graphnodes = new List<GraphElement>();
            foreach (var item in graphElements)
            {
                if (item != mRoot)
                    graphnodes.Add(item);
            }
            DeleteElements(graphnodes);

            var dirty = false;
            if (mAsset != null)
            {
                // create nodes
                graphnodes.Clear();
                var len = mAsset.m_Nodes == null ? 0 : mAsset.m_Nodes.Length;
                StoryNodeVisual start = null;
                for (int i = 0; i < len; i++)
                {
                    var nodeasset = mAsset.m_Nodes[i];
                    if (nodeasset == null)
                        continue;
                    var info = StoryNodeLib.GetNodeInfo(nodeasset.GetType());
                    if (info == null)
                        continue;
                    var node = CreateGraphNode(info, nodeasset, nodeasset.m_Position);
                    if (node != null)
                    {
                        graphnodes.Add(node);
                        AddElement(node);
                        if (i == mAsset.m_StartNode)
                            start = node as StoryNodeVisual;
                    }
                }
                if (start != null)
                    NewEdge<Edge>(start.startPort, startNode.actionPort);
                foreach (var nodeasset in mAsset.m_Nodes)
                {
                    if (nodeasset == null)
                        continue;
                    if (nodeasset is TaskNode tnode)
                    {
                        var next = GetNode(graphnodes, tnode) as StoryNodeVisual;
                        if (next == null)
                            continue;
                        foreach (var precondition in tnode.m_Preconditions)
                        {
                            var pre = GetNode(graphnodes, precondition.node) as StoryNodeVisual;
                            if (pre == null || next == null)
                                continue;
                            var inputPort = next.startPort;
                            var outputPort = pre.GetExitPort(precondition.result);
                            // precondition.flags == EFinishFlag.OnSuccess ? pre.successPort : (precondition.flags == EFinishFlag.OnFailed ? pre.failPort : pre.exitPort);
                            if (outputPort != null)
                                NewEdge<Edge>(inputPort, outputPort);
                        }
                        foreach(var precondition in tnode.m_Killers)
                        {
                            var pre = GetNode(graphnodes, precondition.node) as StoryNodeVisual;
                            if (pre == null || next == null)
                                continue;
                            var inputPort = next.killPort;
                            var outputPort = pre.GetExitPort(precondition.result);
                            // precondition.flags == EFinishFlag.OnSuccess ? pre.successPort : (precondition.flags == EFinishFlag.OnFailed ? pre.failPort : pre.exitPort);
                            if (outputPort != null && inputPort != null)
                                NewEdge<Edge>(inputPort, outputPort);
                        }
                    }
                }
                mRoot.SetPosition(new Rect(mAsset.m_StartPosition, Vector2.zero));
            }
            UpdateDescription();
            //ParallelUtils.Schedule(() => FrameAll(), true);
            IsDirty = dirty;
        }

        public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter)
        {
            mCompatiblePorts.Clear();
            var portDir = startPort.direction;
            var portType = startPort.portType;
            var fromSE = startPort.node == mRoot;
            foreach (var port in ports)
            {
                var isSE = port.node == mRoot;
                if (portDir != port.direction && !(isSE && fromSE) && startPort.portType == port.portType && port.node != startPort.node)
                {
                    mCompatiblePorts.Add(port);
                }
            }
            return mCompatiblePorts;
            //return base.GetCompatiblePorts(startPort, nodeAdapter);
        }

        public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
        {
            evt.menu.AppendAction("Reset", (act) => LoadGraphNodes(), DropdownMenuAction.Status.Normal);
            var graph = asset == null ? null : asset.GetFirstGraph();
            if (graph != null && AttachedWindow is StoryGraphEditor editor)
                evt.menu.AppendAction($"Debug {graph.name}", (act) => editor.Reopen(graph), DropdownMenuAction.Status.Normal);
            base.BuildContextualMenu(evt);
        }

        public void Dispose()
        {
            if(mSelection != null)
            {
                GlobalUtil.Destroy(mSelection);
                mSelection = null;
            }
        }


        public class StartNode : Node
        {
            internal readonly Port actionPort;
            public StartNode() : base()
            {
                name = "story-graph-root";
                title = "FIRST TASK";

                var icon = new VisualElement();
                icon.name = "icon";
                icon.AddToClassList("node-icon");
                titleContainer.Insert(0, icon);

                actionPort = InstantiatePort(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(TaskNode));
                actionPort.portName = "START";
                actionPort.portColor = Color.white;
                outputContainer.Add(actionPort);

                var content = new VisualElement();
                content.name = "node-container";
                content.AddToClassList("node-contents");
                mainContainer.Add(content);

                //var desc = new Label("The story starts here");
                //desc.AddToClassList("node-descript");
                //content.Add(desc);

                capabilities &= ~Capabilities.Deletable;
            }

            //public override Port InstantiatePort(Orientation orientation, Direction direction, Port.Capacity capacity, Type type)
            //{
            //    return Port.Create<Edge>(orientation, direction, capacity, type);
            //}
            public override Port InstantiatePort(Orientation orientation, Direction direction, Port.Capacity capacity, System.Type type)
            {
                var port = new GraphPort<Edge>(orientation, direction, capacity, type);
                port.AutoCreationFilter = StoryNodeLib.NO_FILTER;
                port.IsSingleConnection = true;
                return port;
                //return Port.Create<LinkEdge>(orientation, direction, capacity, type);
            }
        }

    }
}
